package mybatis.testDemo;

import ch.qos.logback.classic.util.ContextInitializer;
import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
import com.github.pagehelper.PageInterceptor;
import context.CompontScan;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import mybatis.interceptor.AddOrgCodeForPageInteceptor;
import mybatis.interceptor.DataSourceRouteInteceptor;
import mybatis.interceptor.SqlPrintInteceptor;
import mybatis.testDemo.datasource.*;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.jdbc.ScriptRunner;
import org.apache.ibatis.mapping.ResultSetType;
import org.apache.ibatis.plugin.Interceptor;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.TestInfo;
import org.mybatis.spring.annotation.MapperScan;
import org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration;
import org.springframework.boot.context.config.ConfigFileApplicationListener;
import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import sample.mybatis.mapper.MapperInterface;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicReference;

import static mybatis.testDemo.MybatisBean.TestTypes.*;
import static mybatis.testDemo.datasource.CustomRouteDataSource.setupSqlSessionFactoryForRoute;
import static mybatis.testDemo.datasource.PgDataSource.setupSqlSessionFactoryForPG;
import static mybatis.testDemo.datasource.PluginDataSource.setupSqlSessionFactoryForRoutePlugin;

@Configuration
@ImportAutoConfiguration(MybatisAutoConfiguration.class)
class MybatisAutoConfigurationDemo {
}

@Configuration
@ImportAutoConfiguration(value = {TransactionAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class})
class TransactionAutoConfigurationDemo {
}


@Slf4j
public class MybatisBean implements DataReset {
    public void runScript(DataSource customRoutingDataSource, String script) throws SQLException, IOException {
        Connection connection = customRoutingDataSource.getConnection();
        ScriptRunner scriptRunner = new ScriptRunner(connection);
        scriptRunner.setAutoCommit(true);
        scriptRunner.setStopOnError(true);

        PrintStream out = System.out;
        PrintWriter printWriter = new PrintWriter(out);

        scriptRunner.setLogWriter(printWriter);
        scriptRunner.setThrowWarning(false);

        Reader resourceAsReader = Resources.getResourceAsReader(script);
        scriptRunner.runScript(resourceAsReader);
        connection.close();
    }

    public void tableCreateTest(MapperInterface mapperInterface, DataSource dataSource) throws SQLException, IOException {
        String script = "create.sql";
        AbstractCustomRoutingDataSource customRoutingDataSource = (AbstractCustomRoutingDataSource) dataSource;
        customRoutingDataSource.loadBalancePolicy(1);
        runScript(dataSource, script);
    }

    @Override
    public void dataReset(MapperInterface mapperInterface,
                          DataSource dataSource,
                          Integer index,
                          final String scriptName,
                          TestTypes typeName) throws SQLException, IOException {
        AbstractCustomRoutingDataSource customRoutingDataSource = (AbstractCustomRoutingDataSource) dataSource;
        customRoutingDataSource.loadBalancePolicy(index);
        log.info("current datasource:{}", dataSource.toString());
// this
        this.getClass();

        if (typeName == PG) {
            mapperInterface.truncate();
        } else if (typeName == ROUTEFORPLUGIN) {
            mapperInterface.truncateForPlugin();
        } else if(typeName==ROUTE){
            System.out.println(this.getClass());
            if( (MybatisBeanTkTest.class.isAssignableFrom(getClass())) ){
                mapperInterface.truncateForTk();
            }
        }

        String script = scriptName;
        runScript(dataSource, script);

    }


    /**
     * the unit test type of  sqlsessionFactory
     */
    public enum TestTypes {
        PG, PLUS, EMBED, ROUTE, ROUTEFORPLUGIN;

        public void doAction(AnnotationConfigApplicationContext applicationContext) throws SQLException {
            if (this == PG) {
                setupSqlSessionFactoryForPG(applicationContext);
            } else if (this == ROUTE) {
                setupSqlSessionFactoryForRoute(applicationContext);
            } else if (this == ROUTEFORPLUGIN) {
                setupSqlSessionFactoryForRoutePlugin(applicationContext);
            } else {
                throw new RuntimeException(this.toString()+" haven't association sqlSessionFactory");

                // MybatisBeanTkTest

            }
        }
    }

    public static AnnotationConfigApplicationContext applicationContext = null;
    public static AtomicReference<TestTypes> testTypes = null;

    @BeforeEach
    public void before(TestInfo testInfo) throws SQLException {

        System.setProperty(ContextInitializer.CONFIG_FILE_PROPERTY, "logback-test.xml");

        applicationContext = new AnnotationConfigApplicationContext();
        ConfigDataApplicationContextInitializer ConfigDataApplicationContextInitializer = new ConfigDataApplicationContextInitializer();
        ConfigDataApplicationContextInitializer.initialize(applicationContext);

        applicationContext.register(ConfigFileApplicationListener.class);
        applicationContext.register(MapperConfiguration.class);

        testTypes = new AtomicReference<>();

        testInfo.getTestClass().ifPresent(cls -> {
            /**
             *  subClass -> class
             */
            if (MybatisBeanForPG.class.isAssignableFrom(cls) ||
                    MybatisBeanOtherTest.class.isAssignableFrom(cls)) {
                testTypes.set(PG);
                applicationContext.register(CustomRouteDataSource.RouteDataSourceConfigurationDemo.class);
                applicationContext.register(TransactionAutoConfigurationDemo.class, PgDataSource.class);
            } else if (PluginUnionTest.class.isAssignableFrom(cls)) {
                applicationContext.register(CustomRouteDataSource.RouteDataSourceConfigurationDemo.class);
                applicationContext.register(TransactionAutoConfigurationDemo.class, CustomRouteDataSource.class);
                testTypes.set(ROUTEFORPLUGIN);
            } else  if(MybatisBeanTkTest.class.isAssignableFrom(cls)){
                testTypes.set(ROUTE);
                applicationContext.register(CustomRouteDataSource.RouteDataSourceConfigurationDemo.class);
                applicationContext.register(TransactionAutoConfigurationDemo.class, CustomRouteDataSource.class);
            }else {
                throw new RuntimeException(cls.toString()+" haven't association startUp");
            }
        });
        testTypes.get().doAction(applicationContext);

    }

    protected void startContext() {
        applicationContext.refresh();
        applicationContext.start();
        // this will throw an exception if the beans cannot be found
        applicationContext.getBean("sqlSessionFactory");
        CompontScan.print(applicationContext);

    }

    @PropertySource(value = "classpath:/application.yml")
    @Data
    @Configuration(proxyBeanMethods = false)
    @MapperScan(basePackages = {"sample.mybatis.mapper", "sample.mybatis.context.domain"})
    public static class MapperConfiguration implements BeanFactoryPostProcessor {


        @Resource
        private MapperInterface mapperInterface1;

        @Resource
        private MapperInterface mapperInterface2;


        /**
         * for mybatis-plus
         */
        @Bean
        public ConfigurationCustomizer configurationCustomizer() {
            return (org.apache.ibatis.session.Configuration con) -> {
                if (con.getClass().isAssignableFrom(MybatisConfiguration.class)) {
                    MybatisConfiguration mybatisConfiguration = (MybatisConfiguration) con;
                    mybatisConfiguration.setDefaultResultSetType(ResultSetType.FORWARD_ONLY);
                }
            };
        }

        public static Interceptor SqlPrintInteceptor() {
            SqlPrintInteceptor sqlPrintInteceptor = new SqlPrintInteceptor();
            sqlPrintInteceptor.setProperties(new Properties());
            return sqlPrintInteceptor;
        }

        public static Interceptor DataSourceRouteInteceptor(CustomRoutingDataSource customRoutingDataSource) {
            DataSourceRouteInteceptor dataSourceRouteInteceptor = new DataSourceRouteInteceptor(customRoutingDataSource);
            dataSourceRouteInteceptor.setProperties(new Properties());
            return dataSourceRouteInteceptor;
        }

        public static Interceptor AddOrgCodeForPageInteceptor() {
            AddOrgCodeForPageInteceptor addOrgCodeInteceptor = new AddOrgCodeForPageInteceptor();
            addOrgCodeInteceptor.setProperties(new Properties());
            return addOrgCodeInteceptor;
        }

        public static Interceptor PageInterceptor() {
            PageInterceptor pageInterceptor = new PageInterceptor();

            pageInterceptor.setProperties(new Properties());
            return pageInterceptor;
        }


        @Bean
        public static DruidDataSource druidDataSource() {
            DruidDataSource ds = new DruidDataSource();
            ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
            ds.setUrl(
                    "jdbc:mysql://localhost:3306/test?characterEncoding=utf8&useSSL=true&serverTimezone=Asia" +
                            "/Shanghai");
            ds.setUsername("root");
            ds.setPassword("root");
            ds.setInitialSize(5);
            return ds;
        }

        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
            if (testTypes.get() == PLUS) {
                OptimisticLockerInterceptor optimisticLockerInterceptor = new OptimisticLockerInterceptor();
                beanFactory.registerSingleton("optimisticLockerInterceptor", optimisticLockerInterceptor);
                /**
                 optimisticLockerInterceptor     instead of the  abstractRoutingDataSource ,?
                 BeanDefinition pgDruidDataSource = beanFactory.getBeanDefinition("pgDruidDataSource");
                 pgDruidDataSource.setPrimary(false);
                 BeanDefinition dataSource = beanFactory.getBeanDefinition("dataSource");
                 dataSource.setPrimary(true);
                 */
            }

        }

    }


}
